/**
 * Copyright (C) 2016 Eshore Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.eshore.thrpc.config;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.TServiceClientFactory;

import com.eshore.thrpc.RpcException;
import com.eshore.thrpc.client.DefaultInvoker;
import com.eshore.thrpc.client.Invoker;
import com.eshore.thrpc.common.ServerNode;
import com.eshore.thrpc.heartbeat.HeartBeatManager;
import com.eshore.thrpc.pool.TServiceClientPoolFactory;
import com.eshore.thrpc.proxy.DynamicClientHandler;

/**
 * 服务消费者配置
 * <p>
 * 
 * @author : zhouzhixiang
 * @date : 2016-11-20
 */
public class ClientConfig<T> implements IConfigCheck {

    /** 服务名 */
    private String name;

    /** 服务负责人 */
    private String owner;

    /** 服务名(全称)：命名空间$服务名简称 */
    private String service;

    /** 服务地址，使用这个参数表示使用的直连的方式 */
    private String address;

    /** 重试次数，默认0次 */
    private int retry = 0;

    /** 完整接口名 */
    private String iface;

    /**
     * is block or not
     */
    private boolean isNonBlock = true;

    /** 协议 */
    private String protocol = "thrift";

    /** 负载均衡策略，默认为round，可选：round和random */
    private String loadbalance = "round";

    // 下面的配置项是heartbeat的基本配置
    /** 启动推迟执行时间，毫秒。默认1s。 */
    private int heartbeatDelay = 1000;

    /** 心跳频率,单位为ms，默认为1s */
    private int heartbeatInterval = 20000;


    // 下面的配置项是连接池的基本配置
    /** 超时时间，单位为ms，默认为3s */
    private int timeout = 10000;

    /** 最大活跃连接数200 */
    private int maxActive = 1024;

    /** 链接池中最大空闲的连接数,默认为100 */
    private int maxIdle = 100;

    /** 连接池中最少空闲的连接数,默认为0 */
    private int minIdle = 0;

    /** 当连接池资源耗尽时，调用者最大阻塞的时间 */
    private int maxWait = 2000;

    /** 空闲链接”检测线程，检测的周期，毫秒数，默认位1min，-1表示关闭空闲检测 */
    private int timeBetweenEvictionRunsMillis = 60000;

    /** 空闲时是否进行连接有效性验证，如果验证失败则移除，默认为false */
    private boolean testWhileIdle = true;


    /** {@link GenericKeyedObjectPool} */
    private GenericKeyedObjectPool<ServerNode, TServiceClient> pool;

    private int maxTotal = 1024;


    @SuppressWarnings({"unchecked", "rawtypes"})
    public T createProxy() throws Exception {
        check();

        if (getAddress() == null) {
            throw new RpcException(RpcException.CONFIG_EXCEPTION,
                    "The params 'service' cannot empty!");
        }

        String[] addresss = getAddress().split(":");
        ServerNode clientNode =
                new ServerNode(addresss[0], Integer.parseInt(addresss[1]), isNonBlock);

        if (name != null && !"".equals(name.trim())) {
            clientNode.setName(name);
        }
        HeartBeatManager<T> heartBeatManager =
                new HeartBeatManager<T>(getHeartbeatDelay(),
                        getHeartbeatInterval(), pool);
        heartBeatManager.setServerNode(clientNode);
        heartBeatManager.startHeatbeatTimer();
        
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 加载Iface接口
        Class<?> objectClass = classLoader.loadClass(iface);

        GenericKeyedObjectPool<ServerNode, TServiceClient> pool =
                buildClientPool(classLoader, objectClass);

        this.pool = pool;


        /*
         * HeartBeatManager<T> heartBeatManager = new HeartBeatManager<T>(heartbeat,
         * heartbeatTimeout, heartbeatTimes, heartbeatInterval, pool);
         * heartBeatManager.setServerNode(clientNode); heartBeatManager.startHeatbeatTimer();
         */
        addShutdownHook();
        // return (T) pool.borrowObject(clientNode);
        Invoker invoker = new DefaultInvoker(clientNode, pool, getRetry());
        DynamicClientHandler dynamicClientHandler = new DynamicClientHandler(invoker);
        return dynamicClientHandler.bind(classLoader, objectClass);
    }


    /**
     * 构建client对象池
     * <p>
     * 
     * @param classLoader
     * @param ifaceClass
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws RpcException
     */
    @SuppressWarnings("unchecked")
    protected GenericKeyedObjectPool<ServerNode, TServiceClient> buildClientPool(
            ClassLoader classLoader, Class<?> ifaceClass) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException, RpcException {
        // 设置poolConfig
        GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig();
        poolConfig.setMaxTotal(maxTotal);
        poolConfig.setMaxWaitMillis(maxWait);
        poolConfig.setMaxTotalPerKey(maxTotal);
        poolConfig.setMinIdlePerKey(minIdle);
        poolConfig.setMaxIdlePerKey(maxIdle);
        poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        poolConfig.setTestWhileIdle(testWhileIdle);
        poolConfig.setTestOnBorrow(true);
        poolConfig.setBlockWhenExhausted(true);
        poolConfig.setLifo(false);
        

        Class<TServiceClientFactory<TServiceClient>> fi =
                (Class<TServiceClientFactory<TServiceClient>>) classLoader
                        .loadClass(findOutClassName() + "$Client$Factory");
        TServiceClientFactory<TServiceClient> clientFactory = fi.newInstance();
        TServiceClientPoolFactory clientPool =
                new TServiceClientPoolFactory(clientFactory, timeout);

        return new GenericKeyedObjectPool<ServerNode, TServiceClient>(clientPool, poolConfig);
    }

    /**
     * 获取外部内的类名
     * <p>
     * 
     * @param iface
     * @return 类名
     */
    private String findOutClassName() {
        if (iface.contains("$")) {
            return iface.substring(0, iface.indexOf("$"));
        }
        return iface;
    }

    @Override
    public void check() throws RpcException {
        if (StringUtils.isEmpty(service)) {
            throw new RpcException(RpcException.CONFIG_EXCEPTION,
                    "The params 'service' cannot empty!");
        }
        if (StringUtils.isEmpty(iface)) {
            throw new RpcException(RpcException.CONFIG_EXCEPTION,
                    "The params 'iface' cannot empty!");
        }
    }

    /**
     * 销毁资源<br/>
     * 包括：释放注册中心连接、heartbeat等资源。
     * <p>
     */
    public void destory() {
        if (pool != null) {
            pool.clear();
        }
    }

    /**
     * 添加关闭钩子
     * <p>
     * 
     * @param registry
     * @param monitor
     * @param heartBeatManager
     */
    protected void addShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override
            public void run() {
                if (pool != null) {
                    pool.close();
                    pool.clear();
                }
            }
        }));
    }

    /**
     * getter method
     * 
     * @see ClientConfig#name
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#name
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#owner
     * @return the owner
     */
    public String getOwner() {
        return owner;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#owner
     * @param owner the owner to set
     */
    public void setOwner(String owner) {
        this.owner = owner;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#service
     * @return the service
     */
    public String getService() {
        return service;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#service
     * @param service the service to set
     */
    public void setService(String service) {
        this.service = service;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#address
     * @return the address
     */
    public String getAddress() {
        return address;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#address
     * @param address the address to set
     */
    public void setAddress(String address) {
        this.address = address;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#retry
     * @return the retry
     */
    public int getRetry() {
        return retry;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#retry
     * @param retry the retry to set
     */
    public void setRetry(int retry) {
        this.retry = retry;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#iface
     * @return the iface
     */
    public String getIface() {
        return iface;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#iface
     * @param iface the iface to set
     */
    public void setIface(String iface) {
        this.iface = iface;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#protocol
     * @return the protocol
     */
    public String getProtocol() {
        return protocol;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#protocol
     * @param protocol the protocol to set
     */
    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#loadbalance
     * @return the loadbalance
     */
    public String getLoadbalance() {
        return loadbalance;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#loadbalance
     * @param loadbalance the loadbalance to set
     */
    public void setLoadbalance(String loadbalance) {
        this.loadbalance = loadbalance;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#timeout
     * @return the timeout
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#timeout
     * @param timeout the timeout to set
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#maxActive
     * @return the maxActive
     */
    public int getMaxActive() {
        return maxActive;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#maxActive
     * @param maxActive the maxActive to set
     */
    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#maxIdle
     * @return the maxIdle
     */
    public int getMaxIdle() {
        return maxIdle;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#maxIdle
     * @param maxIdle the maxIdle to set
     */
    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#minIdle
     * @return the minIdle
     */
    public int getMinIdle() {
        return minIdle;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#minIdle
     * @param minIdle the minIdle to set
     */
    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#maxWait
     * @return the maxWait
     */
    public int getMaxWait() {
        return maxWait;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#maxWait
     * @param maxWait the maxWait to set
     */
    public void setMaxWait(int maxWait) {
        this.maxWait = maxWait;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#timeBetweenEvictionRunsMillis
     * @return the timeBetweenEvictionRunsMillis
     */
    public int getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#timeBetweenEvictionRunsMillis
     * @param timeBetweenEvictionRunsMillis the timeBetweenEvictionRunsMillis to set
     */
    public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    /**
     * getter method
     * 
     * @see ClientConfig#testWhileIdle
     * @return the testWhileIdle
     */
    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    /**
     * setter method
     * 
     * @see ClientConfig#testWhileIdle
     * @param testWhileIdle the testWhileIdle to set
     */
    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isNonBlock() {
        return isNonBlock;
    }

    public void setNonBlock(boolean isNonBlock) {
        this.isNonBlock = isNonBlock;
    }

    public GenericKeyedObjectPool<ServerNode, TServiceClient> getPool() {
        return pool;
    }

    public void setPool(GenericKeyedObjectPool<ServerNode, TServiceClient> pool) {
        this.pool = pool;
    }
    
    
    public int getHeartbeatDelay() {
        return heartbeatDelay;
    }


    public void setHeartbeatDelay(int heartbeatDelay) {
        this.heartbeatDelay = heartbeatDelay;
    }


    public int getHeartbeatInterval() {
        return heartbeatInterval;
    }


    public void setHeartbeatInterval(int heartbeatInterval) {
        this.heartbeatInterval = heartbeatInterval;
    }


    public int getMaxTotal() {
        return maxTotal;
    }


    public void setMaxTotal(int maxTotal) {
        this.maxTotal = maxTotal;
    }
    
}
